using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Threading;

using Excel = Microsoft.Office.Interop.Excel;
using Word = Microsoft.Office.Interop.Word;
using Microsoft.Office.Core;

using PowerPoint = Microsoft.Office.Interop.PowerPoint;
using LogAnalyzerIIS.LogClasses;
using LogAnalyzerIIS.Objects;

namespace LogAnalyzerIIS.Exporters
{
	public partial class ExportForm : Form
	{
		// thread for exporting
		Thread thread = null;

		// delegate to invoke update during exporting
		private delegate void VoidDelegate();

		// available export format enumeration
		public enum ExportFormat
		{
			Word,
			Powerpoint
		}

		private ExportFormat exportFormat;
		private string outputFilepath;
		private Excel.Workbook excelWorkbook;

		/// <summary>
		/// Initializes parameters for export Excel 2007 workbook to Word 2007 or PowerPoint 2007 format
		/// </summary>
		/// <param name="wb">Workbook to export</param>
		/// <param name="outPath">Path to output file</param>
		/// <param name="ef">Format to export</param>
		public ExportForm(Excel.Workbook workbook, string outPath, ExportFormat format)
		{
			InitializeComponent();
			excelWorkbook = workbook;

			outputFilepath = outPath;
			exportFormat = format;
		}

		/// <summary>
		/// Thread-safe progress bar step 
		/// </summary>
		private void PerformStep()
		{
			if (!progressBar1.InvokeRequired)
			{
				progressBar1.PerformStep();
			}
			else
			{
				System.Delegate delegatePerformStep = new VoidDelegate(PerformStep);

				this.BeginInvoke(delegatePerformStep, null);
			}
		}

		/// <summary>
		/// Finishes thread work
		/// </summary>
		private void FinalizeThread()
		{
			try
			{
				if (thread != null)
				{
					if (thread.ThreadState != ThreadState.Suspended)
					{
						thread.Abort();
						thread.Join();
						thread = null;
					}
				}
			}
			finally
			{
				Close();
			}
		}

		/// <summary>
		/// Gets dictionary of page breaks and shapes from the Excel worksheet
		/// </summary>
		/// <param name="ws">Excel worksheet</param>
		private static SortedDictionary<int, object> GetBreaksFromWorksheet(Excel.Worksheet excelWorksheet)
		{
			int chartRowNumber;

			// dictionary to store charts and pagebreaks (used as checkpoint for copying)
			SortedDictionary<int, object> Breaks = new SortedDictionary<int, object>();

			// put all the charts into the dictionaty
			foreach (Excel.Shape shape in excelWorksheet.Shapes)
			{
				// get row number where a chart begins
				chartRowNumber = StatisticsWriter.GetVertCellOffset(excelWorksheet, 1, shape.Top) + 1;

				Breaks.Add(chartRowNumber, shape);
			}

			// put all the horizontal pagebreaks into the dictionary
			try
			{
				for (int i = 1; i <= excelWorksheet.HPageBreaks.Count; i++)
				{
					// get row number of the pagebreak
					chartRowNumber = StatisticsWriter.GetVertCellOffset(excelWorksheet, 1, Convert.ToDouble(excelWorksheet.HPageBreaks[i].Location.Top)) + 1;

					Breaks.Add(chartRowNumber, excelWorksheet.HPageBreaks[i]);
				}
			}
			catch { }
			return Breaks;
		}

		/// <summary>
		/// Converts Excel 2007 workbook to Word 2007 document
		/// </summary>
		/// <param name="wb">Excel workbook</param>
		/// <param name="OutPath">Path to output Word file</param>
		public void ConvertToWord(Excel.Workbook workbook, string outPath)
		{
			// create delegate to perform thread-safe progress bar step
			Delegate delegatePerformStep = new VoidDelegate(PerformStep);

			// reset Excel cut/copy mode
			workbook.Application.CutCopyMode = 0;

			// initialize a new hidden instance of Word
			Word.ApplicationClass wordApplication = new Word.ApplicationClass();

			// variables to pass by reference (for the Word methods)
			object missing = Type.Missing;
			object boolVar = true;

			// add new Word document
			Word.Document reportDoc = wordApplication.Documents.Add(ref missing, ref missing, ref missing, ref boolVar);

			// get a range from the whole document
			Word.Range range = reportDoc.Range(ref missing, ref missing);

			// for each worksheet in the workbook
			foreach (Excel.Worksheet excelWorksheet in workbook.Worksheets)
			{
				// dictionary to store charts and pagebreaks (used as checkpoint for copying)
				SortedDictionary<int, object> Breaks = GetBreaksFromWorksheet(excelWorksheet);

				int startRow = 1;
				object pageBreak;
				int columnNumber;
				int endRow = startRow;
				object unit, extend;

				// for each break item
				foreach (KeyValuePair<int, object> breaksKey in Breaks)
				{
					columnNumber = 1;

					// find the width of the region before current break
					for (int i = startRow; i < breaksKey.Key; i++)
					{
						while ((excelWorksheet.Cells[i, columnNumber] as Excel.Range).Value2 != null &&
								(excelWorksheet.Cells[i, columnNumber] as Excel.Range).Value2.ToString() != "")
							columnNumber++;
					}

					unit = Word.WdUnits.wdParagraph;
					extend = Word.WdMovementType.wdMove;

					// get to the end of current range
					range.EndOf(ref unit, ref extend);

					// copy region before the break into the range of Word document
					try
					{
						excelWorksheet.get_Range(excelWorksheet.Cells[startRow, 1], excelWorksheet.Cells[breaksKey.Key - 1, columnNumber--]).Copy(missing);
						range.Paste();
					}
					catch
					{
						range.Text = "Error during converting";
					}
					// get to the end of current Word document range
					range.EndOf(ref unit, ref extend);


					if (breaksKey.Value is Excel.Shape)
					{
						// if break is a chart, paste it and get vertical offset in rows
						try
						{
							(breaksKey.Value as Excel.Shape).Copy();
							range.Paste();
						}
						catch
						{
							range.Text = "Error during convering";
						}
						startRow = breaksKey.Key + StatisticsWriter.GetVertCellOffset(excelWorksheet, breaksKey.Key, (breaksKey.Value as Excel.Shape).Height);
					}
					else
					{
						// if break is pagebreak, insert a pagebreak into the Word document
						pageBreak = Word.WdBreakType.wdPageBreak;
						range.InsertBreak(ref pageBreak);
						startRow = breaksKey.Key;
					}
				}

				// paste trailing region into Word document
				endRow = startRow;

				// if there is 5 adjacent empty rows, we assume we have reached the end of the document
				int emptyRowsCount = 0;
				while (emptyRowsCount < 5)
				{
					// calculate trailing region size
					endRow++;

					if ((excelWorksheet.Cells[endRow, 1] as Excel.Range).Value2 == null || (excelWorksheet.Cells[endRow, 1] as Excel.Range).Value2.ToString() == "")
						emptyRowsCount++;
					else
						emptyRowsCount = 0;
				}

				// get to the end of current Word document range
				unit = Word.WdUnits.wdParagraph;
				extend = Word.WdMovementType.wdMove;
				range.EndOf(ref unit, ref extend);

				columnNumber = 1;

				// calculate trailing region's width
				for (int i = startRow; i < endRow; i++)
				{
					while ((excelWorksheet.Cells[i, columnNumber] as Excel.Range).Value2 != null &&
							(excelWorksheet.Cells[i, columnNumber] as Excel.Range).Value2.ToString() != "")
						columnNumber++;
				}

				// paste trailing region into Word document
				try
				{
					excelWorksheet.get_Range(excelWorksheet.Cells[startRow, 1], excelWorksheet.Cells[endRow - emptyRowsCount + 1, columnNumber]).Copy(missing);
					range.Paste();
				}
				catch
				{
					range.Text = "Error during conversion";
				}

				pageBreak = Word.WdBreakType.wdPageBreak;
				unit = Word.WdUnits.wdParagraph;
				extend = Word.WdMovementType.wdMove;

				// if it's not the last worksheet, insert a pagebreak into Word document
				if (excelWorksheet != workbook.Worksheets[workbook.Worksheets.Count])
				{
					range.EndOf(ref unit, ref extend);
					range.InsertBreak(ref pageBreak);
				}

				// perform progress bar step
				BeginInvoke(delegatePerformStep, null);
			}

			// reset Excel cut/copy mode
			workbook.Application.CutCopyMode = 0;

			// save Word document
			try
			{
				object saveFileName = outPath;

				reportDoc.SaveAs(ref saveFileName, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing, ref missing);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString());
			}

			// show document            
			wordApplication.Visible = true;
			range = reportDoc.Range(ref missing, ref missing);
			range.Font.Size = 11;

			// finalize the thread
			wordApplication.Activate();
			Delegate delegateFinalizeThread = new VoidDelegate(FinalizeThread);
			this.BeginInvoke(delegateFinalizeThread, null);
		}

		/// <summary>
		/// Exports Excel section defined by extractProperties object to PowerPoint
		/// </summary>
		/// <param name="exportProperties">Defines the section to export</param>
		private void pptExportSection(PowerPointExportProperties exportProperties)
		{
			// iterate through current section (until the break cell)
			for (int excelRowNumber = exportProperties.excelStartRowNumber; excelRowNumber < exportProperties.excelEndRowNumber; excelRowNumber++)
			{
				// if current row of PowerPoint table isn't the first, check if table needs to be continued on the next slide
				if (exportProperties.powerPointRowNumber > 1)
				{
					// if the last cell oversteps the limits of the slide, table should be continued on the next slide
					if (exportProperties.shape.Table.Cell(exportProperties.powerPointRowNumber - 1, 1).Shape.Top + exportProperties.shape.Table.Cell(exportProperties.powerPointRowNumber - 1, 1).Shape.Height > exportProperties.slide.Master.Height)
					{
						int powerPointRowCount = exportProperties.shape.Table.Rows.Count;

						// delete excess empty rows from the powerpoint table table
						for (int i = exportProperties.powerPointRowNumber; i < powerPointRowCount; i++)
							exportProperties.shape.Table.Rows[exportProperties.shape.Table.Rows.Count].Delete();

						// delete last data row of the table
						exportProperties.shape.Table.Rows[exportProperties.powerPointRowNumber - 1].Delete();

						// exceed current PowerPoint row's value to enter new table adding IF clause
						exportProperties.powerPointRowNumber = exportProperties.varMaxRows + 1;

						// decrement iteration counter to put deleted row as the first row of new table
						excelRowNumber--;
					}
				}

				// if current PowerPoint table row number exceeds its maximum value, add new slide and table
				if (exportProperties.powerPointRowNumber > exportProperties.varMaxRows)
				{
					// save current table
					PowerPoint.Table table = exportProperties.shape.Table;

					// if this is a slide with section header table, 
					// add table with less than maximum value number of rows to the current slide
					if (exportProperties.varMaxRows == 1)
						exportProperties.shape = exportProperties.slide.Shapes.AddTable(exportProperties.MaxPowerPointTableRows - 1,
								exportProperties.varMaxCols, 0, exportProperties.shape.Top + exportProperties.shape.Height,
								exportProperties.slide.Master.Width,
								exportProperties.slide.Master.Height - exportProperties.shape.Top - exportProperties.shape.Height);
					else
					{
						// otherwise - add new slide and table with maximum number of rows
						exportProperties.slide = exportProperties.reportPres.Slides.Add(exportProperties.SlideIndex++, Microsoft.Office.Interop.PowerPoint.PpSlideLayout.ppLayoutBlank);
						exportProperties.shape = exportProperties.slide.Shapes.AddTable(exportProperties.MaxPowerPointTableRows, exportProperties.varMaxCols, 0, 0, exportProperties.slide.Master.Width, exportProperties.slide.Master.Height);
					}

					exportProperties.powerPointRowNumber = 1;

					// if this is not the first table section, copy table column headers into the new table
					if (exportProperties.varMaxRows != 1)
					{
						for (int c = 1; c <= exportProperties.varMaxCols; c++)
							exportProperties.shape.Table.Cell(1, c).Shape.TextFrame.TextRange.Text = table.Cell(1, c).Shape.TextFrame.TextRange.Text;

						exportProperties.powerPointRowNumber++;
					}

					// if last table was section header, set max table row number less than maximum value
					if (exportProperties.varMaxRows == 1)
						exportProperties.varMaxRows = exportProperties.MaxPowerPointTableRows - 1;
					else
						exportProperties.varMaxRows = exportProperties.MaxPowerPointTableRows;
				}

				// copy values from the Excel worksheet to the PowerPoint table 
				// for each non-empty row in current section
				if ((exportProperties.excelWorksheet.Cells[excelRowNumber, 1] as Excel.Range).Value2 != null)
				{
					for (int c = 1; c <= exportProperties.varMaxCols; c++)
					{
						if ((exportProperties.excelWorksheet.Cells[excelRowNumber, c] as Excel.Range).Value2 != null)
							exportProperties.shape.Table.Cell(exportProperties.powerPointRowNumber, c).Shape.TextFrame.TextRange.Text = (exportProperties.excelWorksheet.Cells[excelRowNumber, c] as Excel.Range).Text.ToString();
					}

					// if current table is section header table, merge all its cells to one
					if (exportProperties.powerPointRowNumber == 1 && exportProperties.varMaxRows == 1 && exportProperties.varMaxCols > 1)
						exportProperties.shape.Table.Cell(1, 1).Merge(exportProperties.shape.Table.Cell(1, exportProperties.varMaxCols));

					exportProperties.powerPointRowNumber++;
				}
			}
		}

		private void pptAddSlide(PowerPointExportProperties exportProperties)
		{
			// calculate the width of current section
			exportProperties.varMaxCols = 1;

			for (int excelRowNumber = exportProperties.excelStartRowNumber; excelRowNumber < exportProperties.excelEndRowNumber; excelRowNumber++)
			{
				int c = exportProperties.varMaxCols;

				while ((exportProperties.excelWorksheet.Cells[excelRowNumber, c] as Excel.Range).Value2 != null)
					c++;

				if (c != exportProperties.varMaxCols)
					exportProperties.varMaxCols = c - 1;
			}

			// add new slide to the presentation and increment slide index
			exportProperties.slide = exportProperties.reportPres.Slides.Add(exportProperties.SlideIndex++, PowerPoint.PpSlideLayout.ppLayoutBlank);

			// add a header table to the slide
			exportProperties.shape = exportProperties.slide.Shapes.AddTable(1, exportProperties.varMaxCols, 0, 0, exportProperties.slide.Master.Width, exportProperties.slide.Master.Height / exportProperties.MaxPowerPointTableRows);

			// reset row numbers
			exportProperties.powerPointRowNumber = 1;
			exportProperties.varMaxRows = 1;
		}

		/// <summary>
		/// Converts Excel 2007 workbook to PowerPoint 2007 document
		/// </summary>
		/// <param name="wb">Excel workbook</param>
		/// <param name="OutPath">Path to output PowerPoint file</param>
		public void ConvertToPowerPoint(Excel.Workbook excelWorkbook, string outPath)
		{
			// create delegate to perform thread-safe progress bar step
			Delegate progressDelegate = new VoidDelegate(PerformStep);

			// reset Excel cut/copy mode
			excelWorkbook.Application.CutCopyMode = 0;

			// initialize a new hidden instance of PowerPoint
			PowerPoint.ApplicationClass powerpointApplication = new PowerPoint.ApplicationClass();

			// variables to pass by reference (for the Word methods)
			object missing = Type.Missing;

			// add new PowerPoint presentation
			PowerPoint.Presentation reportPres = powerpointApplication.Presentations.Add(MsoTriState.msoTrue);

			foreach (Excel.Worksheet worksheet in excelWorkbook.Worksheets)
			{
				SortedDictionary<int, object> Breaks = GetBreaksFromWorksheet(worksheet);
				PowerPointExportProperties exportProperties = new PowerPointExportProperties(reportPres, worksheet);

				// the number of last row occupied with the lowermost chart
				int FinalLimit = 0;

				// indicates that a pagebreak was found but current table hasn't been exported entirely
				bool UnwantedBreak = false;

				// for each page break or shape in the collected dictionary
				foreach (KeyValuePair<int, object> breakKey in Breaks)
				{
					// if current item is horizontal page break
					if (breakKey.Value is Excel.HPageBreak)
					{
						exportProperties.excelEndRowNumber = breakKey.Key;

						// if previous table has been exported entirely
						if (!UnwantedBreak)
							pptAddSlide(exportProperties);

						pptExportSection(exportProperties);

						// jump over the break
						exportProperties.excelStartRowNumber = breakKey.Key;
					}
					else
					{
						// if current item is a shape, add it to the new slide
						exportProperties.slide = reportPres.Slides.Add(exportProperties.SlideIndex++, Microsoft.Office.Interop.PowerPoint.PpSlideLayout.ppLayoutBlank);
						(breakKey.Value as Excel.Shape).Copy();
						PowerPoint.ShapeRange sr = exportProperties.slide.Shapes.Paste();

						// stretch shape over the whole slide
						sr.Width = exportProperties.slide.Master.Width;
						sr.Height = exportProperties.slide.Master.Height;

						// save the number of last row occupied by the shape
						FinalLimit = breakKey.Key + StatisticsWriter.GetVertCellOffset(worksheet, 1, (breakKey.Value as Excel.Shape).Height);
					}

					// check whether current section isn't finished              
					try
					{
						if (breakKey.Key > 1)
							UnwantedBreak = (worksheet.Cells[breakKey.Key - 1, 1] as Excel.Range).Value2 != null && (worksheet.Cells[breakKey.Key, 1] as Excel.Range).Value2 != null && exportProperties.shape != null && exportProperties.slide != null;
					}
					catch
					{
						UnwantedBreak = false;
					}
				}

				// paste trailing region into PowerPoint document
				exportProperties.excelEndRowNumber = exportProperties.excelStartRowNumber;
				int numempty = 0;

				// we think that there're 5 adjacent empty rows, we've riched the end of the document
				// using FinalLimit, take into consideration that cells under a chart are also empty
				while (numempty < 5 || exportProperties.excelEndRowNumber < FinalLimit + 5)
				{
					// calculate trailing region size
					exportProperties.excelEndRowNumber++;

					if ((worksheet.Cells[exportProperties.excelEndRowNumber, 1] as Excel.Range).Value2 == null || (worksheet.Cells[exportProperties.excelEndRowNumber, 1] as Excel.Range).Value2.ToString() == "")
						numempty++;
					else
						numempty = 0;
				}

				// add trailing region
				pptAddSlide(exportProperties);

				pptExportSection(exportProperties);

				BeginInvoke(progressDelegate, null);
			}

			// save PowerPoint document
			try
			{
				reportPres.SaveAs(outPath, Microsoft.Office.Interop.PowerPoint.PpSaveAsFileType.ppSaveAsDefault, Microsoft.Office.Core.MsoTriState.msoFalse);
			}
			catch (Exception ex)
			{
				MessageBox.Show(ex.ToString());
			}
			// show saved document            
			try
			{
				powerpointApplication.Visible = Microsoft.Office.Core.MsoTriState.msoTrue;
				powerpointApplication.Activate();
			}
			finally
			{
				// finalize the thread
				Delegate del1 = new VoidDelegate(FinalizeThread);
				this.BeginInvoke(del1, null);
			}
		}

		private void Exporting_VisibleChanged(object sender, EventArgs e)
		{
			// if the form is showed, initiate exporting process thread
			if (!Visible)
				return;

			progressBar1.Maximum = excelWorkbook.Worksheets.Count;
			progressBar1.Step = 1;

			// ThreadProc();
			thread = new Thread(new ThreadStart(ThreadProc));
			thread.Priority = ThreadPriority.AboveNormal;
			thread.Start();
		}

		private void ThreadProc()
		{
			switch (exportFormat)
			{
				case ExportFormat.Word:
					ConvertToWord(excelWorkbook, outputFilepath);
					break;

				case ExportFormat.Powerpoint:
					ConvertToPowerPoint(excelWorkbook, outputFilepath);
					break;

				default:
					break;
			}
		}

		private void Exporting_FormClosing(object sender, FormClosingEventArgs e)
		{
			if (thread != null)
			{
				thread.Abort();
				thread.Join();
				thread = null;
			}
		}
	}
}